import threading
import gobject
import os
import time

from net import network, bt, queues
from presence import ps
from util.decorators import *
from util import config
from db import db

import message
import control
import receiver
import strategy
import timesync

from util import config

import logging
logger = logging.getLogger('async')

class MessageTable(db.Table):

    # TODO:
    # - Need a way of clearing this table out every so often
    # Or at least only returning the last n expired/delivered objects
    
    def __init__(self):
        db.Table.__init__(self)
        
    def _init(self):
        self._conn.execute('''
            CREATE TABLE messages
            (mid INTEGER PRIMARY KEY NOT NULL, received INTEGER NOT NULL, expired INTEGER, delivered INTEGER) 
            ''')
       
    def _drop(self):
        self._conn.execute('''
            DROP TABLE messages
            ''')

    def log_received(self, mid):
        c = self._conn
        c.execute(''' INSERT INTO messages VALUES (?,?,?,?) ''', (mid, time.time(), None, None))
        
    def log_expired(self, mid):
        c = self._conn
        c.execute(''' UPDATE messages SET expired=? WHERE mid=? ''', (time.time(), mid))

    def log_delivered(self, mid):
        c = self._conn
        c.execute(''' UPDATE messages SET delivered=? WHERE mid=? ''', (time.time(), mid))
    
    def get_expired(self):
        c = self._conn
        return map(self.__map_mid, c.execute(''' SELECT mid FROM messages WHERE expired > ? ''', (time.time(),)))
    
    def get_delivered(self):
        c = self._conn
        return map(self.__map_mid, c.execute(''' SELECT mid FROM messages WHERE delivered > ? ''', (time.time(),)))
        
    def __map_mid(self, dic):
        return dic['mid']
    
class AsyncManager(gobject.GObject):

    __gsignals__ = {
        # Need to document that this is a header object
        'message-received' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_PYOBJECT,)),
    }

    lock = threading.Lock()

    def __init__(self, iface, _presence, config_files=[]):
        gobject.GObject.__init__(self)
        config.add_file(os.path.join(os.path.dirname(__file__), 'async.config'))
        if config_files:
            config.add_files(config_files)
    
        # TODO:
        # -- Determine whether bluetooth is possible
        # -- If so, add the proper meta key, etc
        # -- For now, a hack
        
        bt_mac = '00:15:83:0A:19:AC'
        bt_unicast_port = config.get_base16_int('async.ports', 'bluetooth-unicast')
        bt_broadcast_port = config.get_base16_int('async.ports', 'bluetooth-broadcast')
        
        self.__presence = _presence
        self.__presence.add_meta(config.get_int('async.metas', 'control-port-key'), config.get_int('async.ports', 'control-in'))
        self.__presence.add_meta(config.get_int('async.metas', 'bluetooth-mac-key'), bt_mac)
        self.__presence.add_meta(config.get_int('async.metas', 'bluetooth-unicast-port-key'), bt_unicast_port)
        self.__presence.add_meta(config.get_int('async.metas', 'bluetooth-broadcast-port-key'), bt_broadcast_port)
        self.__presence.add_meta(config.get_int('async.metas', 'unicast-port-key'), config.get_int('async.ports', 'unicast'))
        self.__time_sync = timesync.TimeSync(self.__presence)
        
        self.__complete = {}
        self.__incoming = {}

        self.__busy = False

        self.__ip = network.get_ip_addr(iface)

        self.__receiver = receiver.Receiver()

        self.__control = control.Control(self, iface, config.get_int('async.ports', 'control-in'))
        self.__receiver.connect('message-complete', self.__on_message_complete)
        self.__receiver.connect('need-retransmission', self.__on_need_retransmission)
        self.__receiver.connect('busy', self.__on_busy)

        self.__queues = {
            config.get_int('async.networks', 'unicast') : queues.MessageQueue(),
            config.get_int('async.networks', 'broadcast') : queues.MessageQueue(),
            config.get_int('async.networks', 'bluetooth-unicast') : queues.MessageQueue(),
            config.get_int('async.networks', 'bluetooth-broadcast') : queues.MessageQueue(),
        }
        
        self.retransmission_queue = self.__queues[config.get_int('async.networks', 'unicast')]

        for queue in self.__queues.values():
            queue.connect('busy', self.__on_busy)

        network.Unicast(iface, config.get_int('async.ports', 'unicast'), self.__queues[config.get_int('async.networks', 'unicast')],
                        self.__receiver.receive)

        network.Broadcast(iface, config.get_int('async.ports', 'broadcast'), self.__queues[config.get_int('async.networks', 'broadcast')],
                        self.__receiver.receive)

        #bt.Unicast(config.get_base16_int('async.ports', 'bluetooth-unicast'), 
        #                          self.__queues[config.get_int('async.networks', 'bluetooth-unicast')], self.__receiver.receive)
        
        #bt.Broadcast(config.get_base16_int('async.ports', 'bluetooth-broadcast'),
        #                            self.__queues[config.get_int('async.networks', 'bluetooth-broadcast')], self.__receiver.receive)
        self.__presence_types = {}
        for (name, value) in config.items('async.presence-types'):
            (module, seperator, cls) = value.rpartition('.')
            exec('import ' + module )
            self.__presence_types[int(name)] = eval(value)
            
        self.__strategy = strategy.Strategy()
        self.__rt_strategy = strategy.RetransmissionStrategy()

        self.__strategy_timer = gobject.timeout_add(config.get_int('async', 'strategy-interval-ms'), self.__run_strategy)

        self.__table = db.get_database(config.get('async','db-dir'), config.get('async', 'db-file')).get_or_create(MessageTable)

        for (name, value) in config.items('async.folders'):
            if not os.path.exists(value):
                os.makedirs(value)
            
        # Load persisted messages
        logger.debug('Async active-root: %s', config.get('async.folders', 'active-root'))
        for fname in os.listdir(config.get('async.folders', 'active-root')):
            logger.debug('Loading %s',fname)
            if not os.path.isdir(config.get('async.folders', 'active-root') + fname):
                msg = message.depersist(config.get('async.folders', 'active-root') + fname)
                logger.debug('msg %i is complete? %s',msg.header.mid,msg.is_complete())
                if msg.is_complete():
                    self.__complete[msg.header.mid] = msg
                    self.__presence.add_object(self.__create_presence(msg))
                    if msg.is_expired():
                        self.__handle_expired(msg)
                else:
                    self.__incoming[msg.header.mid] = msg.header
                    self.__receiver.resume_incomplete(msg)
                    self.__presence.add_object(ps.PartialPresence(msg.header.mid))
        
        # Sanity check that we've killed all expired messages
        for msg in self.__complete.values():
            assert not msg.is_expired()
    
        # Advertise expired and delivered messages
        expired, delivered = self.__table.get_expired(), self.__table.get_delivered()
        for mid in expired:
            self.__presence.add_object(ps.ExpiredPresence(mid))
        for mid in delivered:
            self.__presence.add_object(ps.DeliveredPresence(mid))
            
        # Handlers for presence-specific things
        self.__presence.connect('object-appeared', self.__on_object_appeared)
        self.__presence.connect('neighbor-update', self.__on_neighbor)
        self.__presence.connect('neighbor-online', self.__on_neighbor)
        
        self.__object_handlers = {
                                ps.ExpiredPresence : self.__handle_expired_presence,
                                ps.DeliveredPresence : self.__handle_delivered_presence,
                                }
        
        # Not sure that we need a per-usertype handler setup
        #self.__neighbor_handlers = {
        #                        ps.UserPresence : self.__handle_user_presence,
        #                        ps.ServicePresence : self.__handle_service_presence,
        #                        }
        
        self.__neighbor_handlers = (self.__handle_neighbor_presence)
        
    @synchronized(lock)
    def shutdown(self):
        incoming = self.__receiver.shutdown()
        for msg in incoming.values():
            msg.persist()

    @synchronized(lock)
    def __remove(self, msg):
        pass
    
    @synchronized(lock)
    def send_message(self, url, msg_type, dests, meta={}, csize=None, expiration_delta=60*60*24*7, explicit_mid=None): # 7 day expiration
        """
            Sends a message to a set of destinations.
            
            @return: The header of the message. This is of type L(message.Header)
            
        """
        if csize is None:
            csize=config.get_int('async', 'max-chunk-size')
            
        src = self.__presence.me.pid
        msg = message.create_new(url, msg_type, src, dests, meta, csize, expiration_delta, explicit_mid)       
        self.__complete[msg.header.mid] = msg
        self.__presence.add_object(self.__create_presence(msg))
        msg.persist()
        self.__table.log_received(msg.header.mid)
        return msg.header

    def has(self, mid):
        return mid in self.__complete or mid in self.__incoming
    
    # Called by the control channel when wanting to retransmit
    # ---
    def get_message(self, mid):
        return self.__complete[mid]

    def get_retransmission_queue(self):
        return self.__queues[config.RETRANSMISSION_NETWORK]

    @synchronized(lock)
    def register_incoming(self, header):
        # Sometimes we will get headers from multiple people simultaneously.
        # We don't want to kill the presence service by registering multiple of the same 
        # object ids as present, so we will just fail silently.
        if header.mid in self.__incoming or header.mid in self.__complete:
            return
        else:
            header.hops.append(self.__presence.me.pid)
            self.__incoming[header.mid] = header
            self.__presence.add_object(ps.PartialPresence(header.mid))
            self.__receiver.register_incoming(header)
    
    @synchronized(lock)  
    def set_busy(self, busy):
        logger.info('setting busy: %s',busy)
        self.__busy = busy

    def is_busy(self):
        return self.__busy

    def __create_presence(self, msg):
        return self.__presence_types[msg.header.msg_type](msg.header.mid, msg.header.fname)
        
    def __run_strategy(self):
        # Need to create a copy of self.__complete because we are going to remove
        # complete messages that have expired.
        if self.__busy:
            return True
        logger.debug('Running strategy')
        for (guid, msg) in self.__complete.copy().iteritems():
            if msg.is_expired():
                logger.debug('message %i expired', msg.header.mid)
                self.__handle_expired(msg)
                continue
            neighbors = self.__presence.get_neighbors()
            (network_name, targets) = self.__strategy.evaluate(msg, neighbors)
            
            for target in targets:
                if not self.is_busy():
                    logger.debug('Sending control channel send_message mid %s to target %s', msg.header.mid, target)
                    self.__control.send_message(msg, target, self.__queues[network_name])
                else:
                    logger.debug('Wanted send header mid %s to %s but was busy.', msg.header.mid, target)
        return True

    def __handle_expired(self, msg):
        self.__presence.replace_with_new(ps.ExpiredPresence(msg.header.mid))
        if msg.header.mid in self.__complete:
            del self.__complete[msg.header.mid]
        elif msg.header.mid in self.__incoming:
            del self.__incoming[msg.header.mid]
        #msg.destroy_persisted()
        # Log in the database that this has expired
        self.__table.log_expired(msg.header.mid)
        self.__presence.replace_with_new(ps.ExpiredPresence(msg.header.mid))
        
    # Event handlers
    def __on_message_complete(self, gobject, msg):
        msg.decode()
        self.__complete[msg.header.mid] = msg
        self.__presence.replace_with_new(self.__create_presence(msg))
        self.__table.log_received(msg.header.mid)
        logger.info('Received message id %i from %i to %s',msg.header.mid, msg.header.src, msg.header.dests)
        if msg.is_for(self.__presence.me.pid):
            logger.info('Message id %i was for me', msg.header.mid)
            self.emit('message-received', msg.header)
            

    def __on_need_retransmission(self, gobject, header, seqs):
        logger.debug('Detected that I need retransmission for message id %i. Seqs: %s', header.mid, seqs)
        neighbors = self.__presence.get_neighbors()
        (network, target) = self.__rt_strategy.evaluate(header.mid, seqs, neighbors)
        if target is None:
            return
        if not self.is_busy():
            logger.debug('Sending control message request_retransmission mid %s seqs %s to target %s', header.mid, seqs, target)
            self.__control.request_retransmission(header, seqs, target, self.__presence.me)

    def __on_busy(self, source, busy):
        self.set_busy(busy)
        
    def __on_appeared(self, presence, obj):
        #  Called when an object appears on the presence service
        if isinstance(obj, ps.DeliveredPresence):
            logger.info('Neighbor has DeliveredPresence for mid %i. Setting this message to delivered', obj.pid)
            # Remove from active, 
            # Remove from inactive,
            # Log delivered in the db
            raise NotImplementedError
        pass

    def __on_neighbor(self, presence, neighbor, new=False):
        #handler = self.__neighbor_handlers.get(neighbor.__class__, None)
        #if handler:
        #    handler(neighbor)
        pass
    
    def __on_object_appeared(self, presence, object):
        handler = self.__object_handlers.get(object.__class__, None)
        if handler:
            handler(object)
            
    def __handle_expired_presence(self, expired_presence):
        pass
    
    def __handle_delivered_presence(self, delivered_presence):
        pass
    
    def __handle_neighbor_presence(self, neighbor):
        pass
            
gobject.type_register(AsyncManager)
